# Copyright (c) 2024 胡伯融
# 物品名字显示异常处理方案 is licensed under Mulan PSL v2.
# You can use this software according to the terms and conditions of the Mulan
# PSL v2.
# You may obtain a copy of Mulan PSL v2 at:
#          http://license.coscl.org.cn/MulanPSL2
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY
# KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
# NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
# See the Mulan PSL v2 for more details.
import os
import json
import shutil
import tkinter as tk


# ：返回文件夹内物品编号名字和嵌套关系的字典。
# -函数/return_name_backup：-param_folder_path文件夹路径-返回编号物品的字典
def return_name_backup(param_folder_path: os.PathLike) -> dict:
    folder_index = 0
    file_index = 0
    index_folder = []
    folder_structure = {}
    root_level_last = 0
    for root, folders, file_names in os.walk(param_folder_path):
        relative_path = os.path.relpath(root, param_folder_path)
        current_level = folder_structure
        root_level = relative_path.count(os.sep)
        if relative_path != '.':
            part = relative_path.split(os.sep)[-1]
            if root_level == root_level_last:
                try:
                    index_folder.pop()
                except Exception:
                    pass
            elif root_level < root_level_last:
                remove_count = root_level_last - root_level
                index_folder = index_folder[:len(index_folder) - remove_count -1]
            for folder in index_folder:
                current_level = current_level[folder]
            folder_name = str(folder_index) + "folder" + part
            current_level = current_level.setdefault(folder_name, {})
            index_folder.append(folder_name)
            folder_index += 1
            root_level_last = root_level
        if file_names:
            current_level = current_level.setdefault('files', [])
            for file in file_names:
                if file == "filename_backup.json":
                    continue
                file = str(file_index) + "file" + file
                current_level.append(file)
                file_index += 1
    return folder_structure


# ：应用物品名字的更改。
# -函数/apply_item_name_change：-param_folder_path文件夹路径-返回无
def apply_item_name_change(param_folder_path: os.PathLike) -> None:
    file_index = 0
    folder_list = []
    for root, folders, file_names in os.walk(param_folder_path):
        relative_path = os.path.relpath(root, param_folder_path)
        if relative_path != '.':
            folder_list.append(root)
        for file_name in file_names:
            file = os.path.join(root, file_name)
            if file_name != "filename_backup.json":
                index_file_name = str(file_index) + "file" + file_name
                os.rename(file, os.path.join(os.path.dirname(file), index_file_name))
                file_index += 1
    folder_list.reverse()
    folder_num = len(folder_list)
    for index, folder in enumerate(folder_list, 1):
        index = folder_num - index
        folder_name = os.path.basename(folder)
        index_folder_name = str(index) + "folder" + folder_name
        os.rename(folder, os.path.join(os.path.dirname(folder), index_folder_name))
            
            
# ：返回编号和物品原名的组合。
# -函数/return_index_and_original_name：-param_name名字-param_index_type编号类别-返回编号和物品名字构成的元组
def return_index_and_original_name(param_name: str, param_index_type: str="file") -> tuple:
    index = param_name.split(param_index_type)[0]
    param_name = param_index_type.join(param_name.split(param_index_type)[1:])
    try:
        int(index[0])
    except Exception:
        raise AssertionError
    return index + param_index_type, param_name


# ：返回文件夹中物品路径的列表。
# -函数/return_items_path：-param_item_folder_path物品文件夹路径-param_item_type物品类别-param_ignore_hidden_file忽略隐藏文件-返回物品路径的列表
def return_items_path(param_item_folder_path: os.PathLike, param_item_type: bool=None, param_ignore_hidden_file: bool=False) -> list:
    ret_list = []
    hidden_list = []
    for root, dirs, files in os.walk(param_item_folder_path):
        if param_item_type is None:
            for file in files:
                    if str(file).startswith('.'):
                        hidden_list.append(os.path.join(root, file))
                    else:
                        ret_list.append(os.path.join(root, file))
            if str(root).startswith('.'):
                    hidden_list.append(root)
            else:
                ret_list.append(root)
        else:
            if param_item_type is True:
                for file in files:
                    if str(file).startswith('.'):
                        hidden_list.append(os.path.join(root, file))
                    else:
                        ret_list.append(os.path.join(root, file))
            else:
                if str(root).startswith('.'):
                    hidden_list.append(root)
                else:
                    ret_list.append(root)
    if param_item_type is None or param_item_type is False:
        ret_list.remove(param_item_folder_path)
    if param_ignore_hidden_file is False:
            ret_list.extend(hidden_list)
    return ret_list


# ：返回编号和物品名字构成的元组，内层到外层顺序的列表。
# -函数/_return_backup_list：-param_name_backup名字备份-param_current_folder_path当前文件夹路径-param_files_path文件们路径-param_folders_path文件夹们路径-返回无-递归实现，边界条件空字典
def _return_backup_sequence(param_name_backup: dict, param_current_folder_path: os.PathLike, param_files_path: list=None, param_folders_path: list=None) -> None:
    for key, value in param_name_backup.items():
        if key == "files" and isinstance(value, list):
            for filename in value:
                index, name = return_index_and_original_name(filename)
                file = os.path.join(param_current_folder_path, name)
                param_files_path.append((index, file))
        else:
            index, name = return_index_and_original_name(key, "folder")
            current_folder = os.path.join(param_current_folder_path, name)
            param_folders_path.append((index, current_folder))
            _return_backup_sequence(value, current_folder, param_files_path, param_folders_path)


# ：返回全部物品路径符合内层到外层顺序的列表。
# -函数/return_backup_list：-param_name_backup名字备份-param_current_folder_path当前文件夹路径-param_files_path文件们路径-param_folders_path文件夹们路径-返回物品路径的列表
def return_backup_sequence(param_name_backup: dict, param_current_folder_path: os.PathLike, param_files_path: list=None, param_folders_path: list=None) -> list:
    if param_files_path is None:
        param_files_path = []
    if param_folders_path is None:
        param_folders_path = []
    _return_backup_sequence(param_name_backup, param_current_folder_path, param_files_path, param_folders_path)
    return param_folders_path + param_files_path 


# ：施加面向物品的改变。
# -函数/apply_recovery：-param_backup_data备份数据-param_folder_path文件夹路径-返回无-采用创建文件夹和移动文件的方案，放弃原位修改的方案
def apply_recovery(param_backup_data: list, param_folder_path: os.PathLike) -> None:
    folders = return_items_path(param_folder_path, False)
    files = return_items_path(param_folder_path, True)
    items = {}
    for folder in folders:
        folder_name = os.path.basename(folder)
        index, _ = return_index_and_original_name(folder_name, "folder")
        items[index] = folder
    for file in files:
        file_name = os.path.basename(file)
        if file_name == "filename_backup.json":
            continue
        index, _ = return_index_and_original_name(file_name)
        items[index] = file
    for index, backup_item in param_backup_data:
        item = items.get(index)
        if item:
            if "folder" in index:
                os.makedirs(backup_item, exist_ok=True)
            else:
                shutil.move(item, backup_item)
    "禁止文件遗漏，采用os.rmdir()组合folders.reverse()。"
    folders.reverse()
    for folder in folders:
        try:
            os.rmdir(folder)
        except Exception:
            pass


# ：备份物品的名字。
# -函数/recover_item_name：-param_folder_path文件夹路径-param_item_name_charset物品名字字符集-返回无
def backup_item_name(param_folder_path: os.PathLike, param_item_name_charset: str="gbk") -> None:
    backup_dict = return_name_backup(param_folder_path)
    backup_file = os.path.join(param_folder_path, "filename_backup.json")
    with open(backup_file, "wt", encoding=param_item_name_charset) as backup_file:
        json.dump(backup_dict, backup_file, ensure_ascii=False, indent=4)
    apply_item_name_change(param_folder_path)


# ：恢复物品的名字。
# -函数/recover_item_name：-param_folder_path文件夹路径-param_item_name_charset物品名字符集-返回无
def recover_item_name(param_folder_path: os.PathLike, param_item_name_charset: str="gbk") -> None:
    backup_file = os.path.join(param_folder_path, "filename_backup.json")
    backup_data = ""
    with open(backup_file, "r", encoding=param_item_name_charset) as backup_file:
        backup_data = json.load(backup_file)
    backup = return_backup_sequence(backup_data, param_folder_path)
    apply_recovery(backup, param_folder_path)


# ：配置图形界面的参数。
# ***Tk：tkinter包中的类，表示图形界面主窗口。
# *主窗口：Tk实例。
# -函数/graphical_user_interface：-param_main_window主窗口-返回无
def graphical_user_interface(param_main_window: tk.Tk) -> None:
    param_main_window.title("Solution处理方案")
    param_main_window.geometry("240x220")
    
    charset_label = tk.Label(param_main_window, text="Charset字符集：")
    charset_label.pack()
    charset_entry = tk.Entry(param_main_window)
    charset_entry.insert(0, "gbk")
    charset_entry.pack()
    
    backup_label = tk.Label(param_main_window, text="BackupTargetFolder备份目标文件夹：")
    backup_label.pack()
    backup_entry = tk.Entry(param_main_window)
    backup_entry.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "example_folder"))
    backup_entry.pack()
    
    recovery_label = tk.Label(param_main_window, text="RecoveryTargetFolder恢复目标文件夹：")
    recovery_label.pack()
    recovery_entry = tk.Entry(param_main_window)
    recovery_entry.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "example_folder"))
    recovery_entry.pack()
    
    def backup():
        backup_folder = backup_entry.get()
        charset = charset_entry.get()
        backup_item_name(backup_folder, charset)
        
    def recover():
        recovery_folder =recovery_entry.get()
        charset = charset_entry.get()
        recover_item_name(recovery_folder, charset)
    
    backup_button = tk.Button(param_main_window, text="Backup备份", command=backup)
    backup_button.pack()
    
    backup_button = tk.Button(param_main_window, text="Recover恢复", command=recover)
    backup_button.pack()


# ：运行图形界面循环。
# -函数/main。
def main():
    main_window = tk.Tk()
    graphical_user_interface(main_window)
    main_window.mainloop()


if __name__ == "__main__":
    main()
